﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;

namespace PDX
{
    /// <summary>
    /// A default <see cref="IPropertyModel"/> created by the
    /// <see cref="DefaultPropertyModelFactory"/>.
    /// </summary>
    /// <remarks>
    /// If you need to override the default property model, you will want
    /// to override this class and then provide the typed version (like
    /// <see cref="DefaultPropertyModel{T}"/>). You will also need to override
    /// the <see cref="DefaultPropertyModelFactory"/>.
    /// </remarks>
    public class DefaultPropertyModelBase : INPC, IPropertyModel
    {
        ViewmodelMetadata metadata = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultPropertyModelBase"/> class.
        /// </summary>
        /// <param name="resolution">The resolution.</param>
        public DefaultPropertyModelBase(IPropertyResolution resolution)
        {
            Ext = new Dictionary<string, object>();

            this.Resolution = resolution;

            SetDisplayName();

            PreviousValue = UntypedValue;

            metadata = ViewmodelMetadata.Get(this);
            metadata.Notifier.PropertyChanged += new PropertyChangedEventHandler(Notificator_PropertyChanged);
        }

        void Notificator_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            CheckPropChanged();
        }

        /// <summary>
        /// Determines the <see cref="DisplayName"/>.
        /// </summary>
        protected virtual void SetDisplayName()
        {
            // default to prop name
            DisplayName = Property.Name;

            // if DisplayName attribute is set, use it
            var attr = Property.GetSingleAttribute<DisplayNameAttribute>();
            if (attr != null)
            {
                DisplayName = attr.DisplayName;
            }
        }

        /// <summary>
        /// Gets the resolution.
        /// </summary>
        public IPropertyResolution Resolution { get; private set; }

        /// <summary>
        /// Gets Resolution.OriginalDC for convenience.
        /// </summary>
        public object OriginalDC { get { return Resolution.OriginalDC; } }

        /// <summary>
        /// Gets Resolution.ResolvedDC for convenience.
        /// </summary>
        public object ResolvedDC { get { return Resolution.ResolvedDC; } }

        object PreviousValue { get; set; }

        /// <summary>
        /// Gets or sets the value of the property, untyped.
        /// </summary>
        /// <value>
        /// The untyped value.
        /// </value>
        public object UntypedValue
        {
            get
            {
                return Property.GetValue(ResolvedDC, null);
            }
            set
            {
                // record the last known value
                PreviousValue = UntypedValue;

                // let the notifier know that the user input is being
                // routed through a property model, and it can behave transactionally
                // (avoid raising multiple notifications)
                metadata.Notifier.BeginTransaction();
                Property.SetValue(ResolvedDC, value, null);
                metadata.Notifier.EndTransaction();
            }
        }

        /// <summary>
        /// A dictionary that allows the attaching of various bindable aspects.
        /// </summary>
        public Dictionary<string, object> Ext { get; private set; }

        /// <summary>
        /// Gets Resolution.Property for convenience
        /// </summary>
        public PropertyInfo Property { get { return Resolution.Property; } }

        /// <summary>
        /// Gets or sets the display name.
        /// </summary>
        /// <value>
        /// The display name.
        /// </value>
        public string DisplayName { get; protected set; }

        /// <summary>
        /// Checks if the property value has changed, and raises
        /// notification if so.
        /// </summary>
        public void CheckPropChanged()
        {
            // TODO use EqualityComparer<T>.Default?

            var prev = PreviousValue;
            var current = UntypedValue;
            bool differs = false;

            if (prev == null)
            {
                if (current != null)
                {
                    differs = true;
                }
            }
            else if (!prev.Equals(current))
            {
                differs = true;
            }

            if (differs)
            {
                OnPropertyChanged("Value");
                PreviousValue = UntypedValue;
            }
        }
    }

    /// <summary>
    /// A typed instantiation of a <see cref="DefaultPropertyModelBase"/>.
    /// The reason is to expose the <see cref="Value"/> property with the
    /// correct type to keep the binding engine happy.
    /// </summary>
    /// <typeparam name="T">The type of the property</typeparam>
    public class DefaultPropertyModel<T> : DefaultPropertyModelBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultPropertyModel&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="resolution">The resolution.</param>
        public DefaultPropertyModel(IPropertyResolution resolution)
            : base(resolution)
        {
        }

        /// <summary>
        /// Gets or sets the value of the property.
        /// </summary>
        /// <value>
        /// The value.
        /// </value>
        public T Value
        {
            get
            {
                return (T)UntypedValue;
            }
            set
            {
                UntypedValue = value;
            }
        }
    }
}
